import asyncio
import inspect
import json
import time
from datetime import datetime
from re import Match
from urllib.parse import parse_qs

from fastapi import Request
from fastapi.routing import APIRoute
from starlette.concurrency import iterate_in_threadpool
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from ua_parser.user_agent_parser import Parse

from app.core.config import default_router_white_list
from app.core.log import logger
from app.enums.log import LogTypeEnum
from app.event.event import event
from app.schemas.log import LogDTO
from app.utils.ip_address import get_searcher
from app.utils.router import matcher


class LoggingMiddleware(BaseHTTPMiddleware):
    """
    操作日志 中间件

    该中间件会拦截所有HTTP请求，过滤白名单路由后，记录请求信息、
    响应结果、执行时间等信息，并通过事件机制异步保存日志。
    """

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
        """
        中间件主入口函数，处理每个 HTTP 请求的日志记录逻辑

        :param request: FastAPI 的 Request 对象，包含客户端请求信息
        :param call_next: 下一个中间件或路由处理函数
        :return: 返回处理后的响应结果
        """
        logger.debug("----------> 4. 操作日志 中间件")
        logger.debug(f"本次请求的请求路径为: {request.url.path}")

        # 判断是否在白名单中，白名单路由不需要记录日志
        if matcher.batch_match(request.url.path, default_router_white_list):
            # 白名单路由，不需记录日志，直接处理请求
            response = await call_next(request)
            logger.debug("<---------- 4. 操作日志 中间件")
            return response

        # 记录请求开始时间，用于计算请求处理耗时
        start_time = time.time()

        # 构造系统日志对象并设置基本信息
        log_dto = _build_log_dto(request)
        log_dto.type = LogTypeEnum.OPERATION.name
        log_dto.params = await _get_request_params(request)

        try:
            # 处理请求
            response = await call_next(request)

            # 记录请求结束时间并计算耗时（毫秒）
            end_time = time.time()
            log_dto.spend_time = int((end_time - start_time) * 1000)

            # 获取响应结果
            response_body = [chunk async for chunk in response.body_iterator]
            response.body_iterator = iterate_in_threadpool(iter(response_body))
            log_dto.result = _handler_response((b''.join(response_body)).decode())

            # 保存请求日志数据 说明：使用后台任务发送信号，不阻塞响应
            asyncio.create_task(event.send_log_event(log_dto))
            logger.debug("<---------- 4. 操作日志 中间件")
            return response
        except Exception as e:
            # 记录请求结束时间并计算耗时（毫秒）
            end_time = time.time()
            log_dto.spend_time = int((end_time - start_time) * 1000)
            # 记录异常信息
            log_dto.exception = str(e.args)
            # 保存请求日志数据 说明：使用后台任务发送信号，不阻塞响应
            asyncio.create_task(event.send_log_event(log_dto))
            raise e


def _build_log_dto(request: Request) -> LogDTO:
    """
    构造系统日志DTO对象，包含请求的基本信息

    :param request: FastAPI请求对象
    :return: LogDTO
    """
    # 路由和请求方式
    url = request.url.path
    http_method = request.method

    # 构建请求日志数据
    log_dto = LogDTO()

    # 获取当前url对应的FastApi路由信息
    url_route: APIRoute | None = None
    route: APIRoute = request.app.router
    for route in route.routes:
        # 通过匹配请求路径和方法找到对应的API路由
        result: Match = route.path_regex.match(url)
        if result is not None and http_method in route.methods:
            url_route = route
            break

    # 如果找到匹配的路由，则提取路由相关信息
    if url_route is not None:
        # 记录操作描述
        log_dto.description = url_route.summary

        # 记录类路径
        func = url_route.endpoint
        try:
            module_name = func.__module__
            method_name = func.__name__
            source_line = inspect.getsourcelines(func)[1]
            log_dto.class_path = f"{module_name}.{method_name}():{source_line}"
        except Exception as e:
            logger.exception(f"Could not get source info: {e}")
    else:
        log_dto.description = ""
        log_dto.class_path = "unknown"

    # 记录请求地址、请求方式、ip 等
    log_dto.url = url
    log_dto.http_method = http_method
    log_dto.ip = request.client.host
    log_dto.ip_region = get_searcher().search_by_ip_str(log_dto.ip)
    log_dto.create_time = datetime.now()

    # 获取UA
    ua = request.headers.get("User-Agent")
    parsed_ua = Parse(ua)
    log_dto.os = parsed_ua['os']['family']
    log_dto.device = f"{parsed_ua['os']['family']} {parsed_ua['os']['major']}"
    log_dto.browser = parsed_ua['device']['family']
    return log_dto


async def _get_request_params(request: Request) -> str | None:
    """
    异步获取请求参数，包括查询参数和请求体参数

    :param request: FastAPI请求对象
    :return: 序列化后的参数JSON字符串，失败时返回None
    """
    params = {}

    # 获取URL参数（查询参数）
    query_params = dict(request.query_params)
    params.update(query_params)

    # 根据请求方法和内容类型获取其他参数
    if request.method in ["POST", "PUT", "PATCH"]:
        content_type = request.headers.get("content-type", "")

        if "application/json" in content_type:
            # 处理JSON参数
            try:
                body = await request.json()
                params.update(body)
            except Exception as e:
                pass
        elif "application/x-www-form-urlencoded" in content_type:
            # 处理表单参数
            try:
                body = await request.body()
                form_data = parse_qs(body.decode())
                # parse_qs返回的是列表，需要处理成单个值
                for key, value in form_data.items():
                    params[key] = value[0] if len(value) == 1 else value
            except Exception as e:
                pass

    try:
        # dict -> json_str
        return json.dumps(params, ensure_ascii=False)
    except Exception as e:
        # 序列化失败时返回None
        return None


def _handler_response(response_text: str) -> str:
    """
    处理响应，尝试格式化JSON响应

    :param response_text: 原始响应文本
    :return: 格式化后的响应文本 json字符串
    """
    try:
        # 尝试解析为JSON对象再序列化为格式化字符串
        response_json = json.loads(response_text)
        response_json_str = json.dumps(response_json, ensure_ascii=False)
        return response_json_str
    except Exception as e:
        # 如果格式化失败，则返回原始响应文本
        return response_text
